package com.xingkeduo.utils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Data
@Slf4j
public class RedisLock implements Lock {

    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 重试时间
     */
    private static final int DEFAULT_ACQUIRY_RETRY_MILLIS = 100;
    /**
     * 锁的后缀
     */
    private static final String LOCK_SUFFIX = "_redis_lock";
    /**
     * 锁的key
     */
    private String lockKey;
    /**
     * 锁超时时间，防止线程在入锁以后，防止阻塞后面的线程无法获取锁
     */
    private int expireMsecs = 15 * 1000;

    private String requestId = UUID.randomUUID().toString().replace("-", "");

    /**
     * 构造器
     *
     * @param redisTemplate
     * @param lockKey       锁的key
     */
    public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey + LOCK_SUFFIX;
    }

    /**
     * 封装和jedis方法
     *
     * @return
     */
    private boolean setNX() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        String a = "if redis.call('setNx',KEYS[1],ARGV[1]) then\n" +
                "    if redis.call('get',KEYS[1])==ARGV[1] then\n" +
                "        return redis.call('expire',KEYS[1],ARGV[2])\n" +
                "    else\n" +
                "        return 0\n" +
                "    end\n" +
                "else\n" +
                "    return 0\n" +
                "end";

        redisScript.setScriptText(a);
        redisScript.setResultType(Long.class);

        // 设置key
        List<String> keyList = new ArrayList<>();
        // key为消息ID
        keyList.add(lockKey);
        Long execute = redisTemplate.execute(redisScript, keyList, requestId, expireMsecs / 1000);
        return execute != null && execute == 1;
    }

    /**
     * 获取锁
     *
     * @return 获取锁成功返回ture，超时返回false
     * @throws InterruptedException
     */


    @Override
    public synchronized void lock() {
        log.info("{}进入锁定{}", this.lockKey, this.requestId);
        int timeout = expireMsecs;
        while (timeout >= 0) {
            //long expires = System.currentTimeMillis() + expireMsecs + 1;
            if (this.setNX()) {
                log.info("锁定成功");
                return;
            }
            timeout -= DEFAULT_ACQUIRY_RETRY_MILLIS;
            // 延时
            try {
                Thread.sleep(DEFAULT_ACQUIRY_RETRY_MILLIS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.info("锁定最终失败");
    }


    /**
     * 释放获取到的锁
     */
    @Override
    public synchronized void unlock() {
        log.info("{}解锁{}", this.lockKey, this.requestId);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();

        String a = "if redis.call('get',KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call('del',KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        // 设置key
        List<String> keyList = new ArrayList<>();
        // key为消息ID
        keyList.add(lockKey);
        redisScript.setScriptText(a);
        redisScript.setResultType(Long.class);
        Long execute = redisTemplate.execute(redisScript, keyList, requestId);
        log.info("{}解锁返回{}", this.lockKey, execute != null && execute == 1);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }


}